#include <wx/wx.h>
#include <iostream>
#include <fstream>
#include <thread>
#include "include.h"

class MyApp :public wxApp
{
public:
    virtual bool OnInit();
};

class MyFrame : public wxFrame
{
public:
    MyFrame();

private:
    void OnHello(wxCommandEvent& event);
    void OnExit(wxCommandEvent& event);
    void OnAbout(wxCommandEvent& event);
};

enum
{
    ID_Hello = 1
};

wxIMPLEMENT_APP(MyApp);

bool MyApp::OnInit()
{
    MyFrame* frame = new MyFrame();
    frame->Show(true);
    return true;
}

MyFrame::MyFrame() :
    wxFrame(NULL, wxID_ANY, "Hello World")
{
    wxMenu* menuFile = new wxMenu;
    menuFile->Append(ID_Hello, "&Hello...\tCtrl-H",
        "Help string shown in status bar for this menu item");
    menuFile->AppendSeparator();
    menuFile->Append(wxID_EXIT);
    
    wxMenu* menuHelp = new wxMenu;
    menuHelp->Append(wxID_ABOUT);

    wxMenuBar* menuBar = new wxMenuBar;
    menuBar->Append(menuFile, "&File");
    menuBar->Append(menuHelp, "&Help");

    SetMenuBar(menuBar);

    CreateStatusBar();
    SetStatusText("Welcome to wxWidgets!");

    Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
    Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
    Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);

    SimMcu *mcu = new SimMcu;
    HexParser::parse(mcu->rom, "sample.hex");
    mcu->start();
}

void MyFrame::OnExit(wxCommandEvent& event)
{
    Close(true);
}

void MyFrame::OnAbout(wxCommandEvent& event)
{
    wxMessageBox("This is a wxWidgets Hello World example",
        "About Hello World", wxOK | wxICON_INFORMATION);
}

void MyFrame::OnHello(wxCommandEvent& event)
{
    wxLogMessage("Hello world from wxWidgets!");
}

Divider::Divider(uint8_t step, std::function<void(void)> f) :
    step(step), f(f)
{
    this->counter = 0;
}

void Divider::clk()
{
    this->counter++;
    if (this->counter >= this->step)
    {
        this->counter = 0;
        this->f();
    }
}

SimMcu::SimMcu():divider(12, std::bind(&SimMcu::clkin, this)),
    timer(1, std::bind(&Divider::clk, &(this->divider)))
{
    r = ram;
    createInsts();
}

void SimMcu::start()
{
    pc = 0;
    ir = rom[pc];

    timer.start();
}

void SimMcu::pause()
{
    timer.pause();
}

void SimMcu::clk()
{
    this->divider.clk();
}

void SimMcu::clkin()
{
    pc += id(ir);
    ir = rom[pc];
}

uint16_t SimMcu::id(uint8_t inst)
{
    return Insts[inst](*this);
}

std::function<uint16_t(SimMcu&)> SimMcu::Insts[256] = { [](SimMcu & mcu)->uint16_t {return 0; } };

void SimMcu::createInsts()
{
#define CAP_LAMBDA  [i](SimMcu& mcu)->uint16_t
#define NOCAP_LAMBDA [](SimMcu & mcu)->uint16_t 

    for (int i = 0; i < 8; i++)
    {
        Insts[0xd8 + i] = CAP_LAMBDA{ int8_t param = mcu.rom[mcu.pc + 1]; --mcu.r[i] ? mcu.pc += param : 0; return 2; };
        Insts[0xe8 + i] = [i](SimMcu& mcu)->uint16_t {return 1; };
        Insts[0x78 + i] = [i](SimMcu & mcu)->uint16_t {uint8_t param = mcu.rom[mcu.pc + 1]; mcu.r[i] = param; return 2; };

        if (i >= 2) continue;

        Insts[0xf6 + i] = CAP_LAMBDA{ mcu.ram[mcu.r[i]] = mcu.acc; return 1; };
    }

    Insts[0x02] = [](SimMcu & mcu)->uint16_t {uint16_t param = (uint16_t)(mcu.rom[mcu.pc + 1]<<8) |mcu.rom[mcu.pc+2]; mcu.pc = param; return 0; };
    Insts[0x22] = NOCAP_LAMBDA{ mcu.pause(); return 1; };
    Insts[0x75] = NOCAP_LAMBDA{ uint16_t param = (uint16_t)(mcu.rom[mcu.pc + 1] << 8) | mcu.rom[mcu.pc + 2]; mcu.ram[param >> 8] = param & 0xff; return 3; };
    Insts[0xb2] = NOCAP_LAMBDA{ uint8_t param = mcu.rom[mcu.pc + 1]; uint8_t p = param % 8; mcu.ram[param - p] ^= 1 << p; return 2; };
    Insts[0xe4] = NOCAP_LAMBDA{ mcu.acc = 0; return 1; };
    Insts[0xe5] = [](SimMcu& mcu)->uint16_t { return 2; };
}

void HexParser::parse(uint8_t* dest, const char* filename)
{
    std::ifstream ifs(filename, std::ifstream::in);

    while (ifs.good())
    {
        char line[256];
        ifs.getline(line, 256);

        uint8_t len = HexParser::atoi((char*)&line[1], 2, 16);
        int startAddr = HexParser::atoi((char*)& line[3], 4, 16);
        int flag = HexParser::atoi((char*)& line[7], 2, 16);

        switch (flag)
        {
        case 0:
            for (int i = 0; i < len; i++)
            {
                dest[startAddr + i] = HexParser::atoi(&line[9 + i * 2], 2, 16);
            }
            break;
        }
    }

    ifs.close();
}

int HexParser::atoi(char* input, int len, uint8_t p)
{
    int result = 0;
    int pow = 1;

    for (int i = len-1; i >=0; i--)
    {
        if ((input[i] >= '0') && (input[i] <= '9'))
        {
            result += (input[i] - '0') * pow;
        }
        else if ((input[i] >= 'a') && (input[i] <= 'f'))
        {
            result += (input[i] - 'a' + 10) * pow;
        }
        else if ((input[i] >= 'A') && (input[i] <= 'F'))
        {
            result += (input[i] - 'A' + 10) * pow;
        }
        else
        {
            return -1;
        }

        pow *= 16;
    }

    return result;
}

Timer::Timer(int interval, std::function<void(void)> expire) :
    interval(interval), expire(expire), state(FALSE)
{
    std::thread t(&Timer::run, this);
    t.detach();
}

void Timer::run()
{
    while (1)
    {
        sleep(this->interval);
        if (state)
        {
            expire();
        }
    }
}

void Timer::start()
{
    state = TRUE;
}

void Timer::pause()
{
    state = FALSE;
}